package cn.xiongxf.service.impl;

import cn.xiongxf.constant.RedisKeyConstant;
import cn.xiongxf.domain.Course;
import cn.xiongxf.domain.CourseMarket;
import cn.xiongxf.domain.CourseOrder;
import cn.xiongxf.domain.CourseOrderItem;
import cn.xiongxf.dto.*;
import cn.xiongxf.exception.GlobalCustomException;
import cn.xiongxf.feign.CourseFeignClient;
import cn.xiongxf.mapper.CourseOrderMapper;
import cn.xiongxf.result.JsonResult;
import cn.xiongxf.service.ICourseOrderItemService;
import cn.xiongxf.service.ICourseOrderService;
import cn.xiongxf.util.AssertUtil;
import cn.xiongxf.util.CodeGenerateUtils;
import cn.xiongxf.vo.CourseOrderDataItemVO;
import cn.xiongxf.vo.CourseOrderDataVO;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import springfox.documentation.spring.web.json.Json;

import java.math.BigDecimal;
import java.util.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author xiongxiaofu
 * @since 2022-08-26
 */
@Service
@Slf4j
@Transactional
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private CourseFeignClient courseFeignClient;
    @Autowired
    private ICourseOrderItemService courseOrderItemService;
    @Autowired
    private RocketMQTemplate mqTemplate;
    @Autowired
    private RedissonClient redissonClient;

    // 抽取方法：根据订单号查询订单
    private CourseOrder selectByOrderNo(String orderNo) {
        return selectOne(new EntityWrapper<CourseOrder>().eq("order_no", orderNo));
    }

    /**
     * 下单，保存订单表和订单明细表
     */
    @Override
    public String placeOrder(CourseOrderDTO dto) {
        // 1.参数校验 @Valid

        // 2.校验令牌token
        // 用户ID todo
        Long loginId = 9L;
        // 获取课程ID列表并转为字符串
        List<Long> courseIds = dto.getCourseIds();
        String courseIdsStr = StringUtils.join(courseIds, ",");  // 集合转字符{1L, 2L, 3L} -> "1, 2, 3"

        // 拼接tocken的key，从Redis中查token
        String tokenKey = "token:" + loginId + ":" + courseIdsStr;
        String token = (String) redisTemplate.opsForValue().get(tokenKey);
        AssertUtil.isNotEmpty(token, "重复下单或下单超时，请重新下单！");
        AssertUtil.isEquals(dto.getToken(), token, "非法请求！");

        // 3.条件校验：课程需为上线状态（feign中做了）

        // 4.查询课程、课程营销，计算总价（feign获取父VO）
        JsonResult jsonResult = courseFeignClient.selectCourseDataForOrder(courseIdsStr);
        // 若返回false，抛异常
        AssertUtil.isTrue(jsonResult.isSuccess(), jsonResult.getMessage());
        // 若返回的data为空，抛异常
        AssertUtil.isNotNull(jsonResult.getData(), "获取课程数据失败！");

        // 处理jsonResult：先转为JSON字符串，再解析为指定类的对象（父VO）
        String jsonString = JSON.toJSONString(jsonResult.getData());  // 注意是jsonResult.getData()
        CourseOrderDataVO vo = JSON.parseObject(jsonString, CourseOrderDataVO.class);

        // 获取总价
        BigDecimal totalAmount = vo.getTotalAmount();
        // 获取子VO
        List<CourseOrderDataItemVO> courseInfos = vo.getCourseInfos();

        // 5.保存订单表和订单明细表
        // 当前时间（用new Date()写在循环内的话一个订单的各明细时间不同）
        Date now = new Date();
        // 订单编号
        String orderNo = CodeGenerateUtils.generateOrderSn(loginId);
        // 拼接标题
        StringBuilder sb = new StringBuilder();
        sb.append("购买课程：");
        // 商品数量
        int totalCount = 0;

        List<CourseOrderItem> items = new ArrayList<>(courseIds.size());
        // 遍历子VO，保存订单明细，并添加到明细列表
        for (CourseOrderDataItemVO courseInfo : courseInfos) {
            Course course = courseInfo.getCourse();
            CourseMarket courseMarket = courseInfo.getCourseMarket();
            // 5.1.创建订单明细对象
            CourseOrderItem item = new CourseOrderItem();

            item.setAmount(courseMarket.getPrice());
            item.setCount(1);
            totalCount = totalCount + item.getCount();  // 累加
            item.setSubtotalAmount(item.getAmount().multiply(new BigDecimal(item.getCount())));

            item.setCourseId(course.getId());
            item.setOrderNo(orderNo);
            item.setCreateTime(now);
            item.setCourseName(course.getName());
            item.setCoursePic(course.getPic());

            sb.append(item.getCourseName()).append("，");

            items.add(item);
        }
        sb.append("支付【" + totalAmount + "】元");

        // 5.2.创建订单对象
        CourseOrder order = new CourseOrder();
        order.setUserId(loginId);
        order.setOrderNo(orderNo);
        order.setCreateTime(now);
        order.setOrderType(dto.getType());
        order.setPayType(dto.getPayType());
        order.setTotalAmount(totalAmount);
        order.setDiscountAmount(new BigDecimal("0"));
        order.setPayAmount(totalAmount.subtract(order.getDiscountAmount()));
        order.setTitle(sb.toString());
        order.setTotalCount(totalCount);
        order.setStatusOrder(CourseOrder.STATE_WAIT_PAY);
        order.setCourseOrderItems(items);

        // 支付单扩展参数
        Map<String, Object> extParams = new HashMap<>();
        extParams.put("loginId", loginId);
        extParams.put("courseIds", courseIds);

        // 构建支付订单消息
        PayOrder2MQDTO payOrder2MQDTO = new PayOrder2MQDTO(
                order.getPayAmount(), order.getPayType(), orderNo, loginId, JSON.toJSONString(extParams), order.getTitle());

        Message<String> payOrderMessage = MessageBuilder.withPayload(JSON.toJSONString(payOrder2MQDTO)).build();

        // 发送MQ事务消息
        TransactionSendResult result = mqTemplate.sendMessageInTransaction(
                "tx-producer-group-courseorder",  // 事务监听器指定的生产者组名
                "topic-payorder:tags-payorder",  // 目的地：topic:tags
                payOrderMessage, // 消息内容message：支付订单
                order);// 扩展参数Object：订单和明细

        log.info("事务消息发送结果：{}，{}", result.getLocalTransactionState(), result.getSendStatus());
        if (result.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE) {
            throw new GlobalCustomException("下单失败！");
        }
        if (result.getSendStatus() != SendStatus.SEND_OK) {
            log.error("发送消息失败：{}", result.getSendStatus());
            // 1.重新发送   2.DB保存发送日志，管理员可手动干预
        }


        // 订单超时处理：下单成功，发送【MQ延迟消息】    延迟等级 1s 5s 10s 30s 1m 2m 3m 4m 5m
        Message<String> message = MessageBuilder.withPayload(orderNo).build();
        SendResult sendResult = mqTemplate.syncSend(
                "topic-payexpire:tags-payexpire", message, 2000, 4);
        if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
            log.error("延迟消息发送失败 {}", sendResult);
            // 1.重试   2.降级   3.记录到DB
        }


        // 删除Redis中的token（防止订单重提）
        redisTemplate.delete(tokenKey);

        // 返回订单号（支付时需要检查）
        return orderNo;
    }


    /**
     * 秒杀下单，保存订单表和订单明细表
     */
    @Override
    public String killPlaceOrder(KillCourseOrderDTO dto) {
        // 1.参数校验 @Valid

        // 获取Redis中的预创单
        Long loginId = 9L;  // 用户ID todo
        String preOrderKey = "order:" + loginId + ":" + dto.getOrderNo();

        // 加分布式锁 - 解决退库存和下单同时成功问题 - 在拿到预创单前上锁
        RLock lock = redissonClient.getLock("lock" + preOrderKey);  // Redis中已经有preOrderKey这个key了
        try {
            lock.lock();
            log.info("加锁成功 ==========================");

            PreCourseOrder2RedisDTO preOrder = (PreCourseOrder2RedisDTO) redisTemplate.opsForValue().get(preOrderKey);
            AssertUtil.isNotNull(preOrder, "订单无效！");

            // 2.校验令牌token
            // 拼接tocken的key，从Redis中查token
            String tokenKey = "token:" + loginId + ":" + preOrder.getCourseId();
            String token = (String) redisTemplate.opsForValue().get(tokenKey);
            AssertUtil.isNotEmpty(token, "重复下单或下单超时，请重新下单！");
            AssertUtil.isEquals(dto.getToken(), token, "非法请求！");

            // 5.保存订单表和订单明细表
            // 当前时间（用new Date()写在循环内的话一个订单的各明细时间不同）
            Date now = new Date();
            // 订单编号
            String orderNo = dto.getOrderNo();
            // 拼接标题

            // 5.1.创建订单明细对象
            CourseOrderItem item = new CourseOrderItem();
            item.setAmount(preOrder.getAmount());
            item.setCount(preOrder.getCount());
            item.setSubtotalAmount(item.getAmount().multiply(new BigDecimal(item.getCount())));
            item.setCourseId(preOrder.getCourseId());
            item.setOrderNo(orderNo);
            item.setCreateTime(now);
            item.setCourseName(preOrder.getCourseName());
            item.setCoursePic(preOrder.getCoursePic());
            // 秒杀ID
            item.setKillCourseId(preOrder.getCourseId());

            // 5.2.创建订单对象
            CourseOrder order = new CourseOrder();
            order.setUserId(loginId);
            order.setOrderNo(orderNo);
            order.setCreateTime(now);
            order.setOrderType(1);
            order.setPayType(dto.getPayType());
            order.setTotalAmount(preOrder.getAmount());
            order.setDiscountAmount(new BigDecimal("0"));
            order.setPayAmount(order.getTotalAmount().subtract(order.getDiscountAmount()));
            order.setTitle("秒杀下单：" + "【" + preOrder.getCourseName() + "】");
            order.setTotalCount(preOrder.getCount());
            order.setStatusOrder(CourseOrder.STATE_WAIT_PAY);
            order.getCourseOrderItems().add(item);

            // 支付单扩展参数
            Map<String, Object> extParams = new HashMap<>();
            extParams.put("loginId", loginId);
            extParams.put("courseIds", Arrays.asList(preOrder.getCourseId()));  // 单个课程ID，处理

            // 构建支付订单消息
            PayOrder2MQDTO payOrder2MQDTO = new PayOrder2MQDTO(
                    order.getPayAmount(),
                    order.getPayType(),
                    orderNo,
                    loginId,
                    JSON.toJSONString(extParams),
                    order.getTitle());

            Message<String> payOrderMessage = MessageBuilder.withPayload(JSON.toJSONString(payOrder2MQDTO)).build();

            // 发送消息
            TransactionSendResult result = mqTemplate.sendMessageInTransaction(
                    "tx-producer-group-courseorder",  // 事务监听器指定的生产者组名
                    "topic-payorder:tags-payorder",  // 目的地：topic:tags
                    payOrderMessage, // 消息内容message：支付订单
                    order);// 扩展参数Object：订单和明细

            log.info("事务消息发送结果：{}，{}", result.getLocalTransactionState(), result.getSendStatus());
            if (result.getLocalTransactionState() != LocalTransactionState.COMMIT_MESSAGE)
                throw new GlobalCustomException("下单失败！");
            if (result.getSendStatus() != SendStatus.SEND_OK)
                log.error("发送消息失败：{}", result.getSendStatus());  // 1.重新发送   2.DB保存发送日志，管理员可手动干预


            // 订单超时处理：下单成功，发送【MQ延迟消息】    延迟等级 1s 5s 10s 30s 1m 2m 3m 4m 5m
            Message<String> message = MessageBuilder.withPayload(orderNo).build();
            SendResult sendResult = mqTemplate.syncSend(
                    "topic-payexpire:tags-payexpire", message, 2000, 4);
            if (sendResult.getSendStatus() != SendStatus.SEND_OK)
                log.error("延迟消息发送失败 {}", sendResult);  // 1.重试   2.降级   3.记录到DB

            // 删除Redis中的token（防止订单重提）
            redisTemplate.delete(tokenKey);
            // 删除Redis中的预创单
            redisTemplate.delete(preOrderKey);

            // 返回订单号（支付时需要检查）
            return orderNo;

        } finally {
            if (lock.isLocked()) lock.unlock();
            log.info("释放锁成功 =============================");
        }


    }


    /**
     * 保存课程订单和订单明细（本地事务）
     */
    @Override
    public void saveOrderAndItems(CourseOrder courseOrder) {
        // 校验订单是否已存在
        CourseOrder courseOrderDB = selectOne(new EntityWrapper<CourseOrder>().eq("order_no", courseOrder.getOrderNo()));
        AssertUtil.isNull(courseOrderDB, "订单已存在！");
        // 保存订单
        insert(courseOrder);

        // 设置关联的订单ID
        courseOrder.getCourseOrderItems().forEach(item -> {
            item.setOrderId(courseOrder.getId());
        });

        // 保存订单明细
        courseOrderItemService.insertBatch(courseOrder.getCourseOrderItems());
    }


    /**
     * 支付结果处理
     */
    public void payResultHandler(PayResult2MQDTO dto) {
        // 1.查询课程订单
        CourseOrder order = selectByOrderNo(dto.getOrderNo());
        AssertUtil.isNotNull(order, "订单无效！");

        // 2.判断状态必须是待支付
        AssertUtil.isEquals(order.getStatusOrder(), CourseOrder.STATE_WAIT_PAY, "订单已被处理！");

        // 3.修改订单状态
        order.setStatusOrder(dto.isSuccess() ? CourseOrder.STATE_SUCCESS_PAY : CourseOrder.STATE_OVERTIME_CANCEL);
        order.setUpdateTime(new Date());
        updateById(order);
    }


    /**
     * 订单超时处理：取消订单
     */
    @Override
    public void cancelOrder(String orderNo) {
        CourseOrder courseOrder = selectByOrderNo(orderNo);
        AssertUtil.isNotNull(courseOrder, "订单无效！");

        if (courseOrder.getStatusOrder().equals(CourseOrder.STATE_WAIT_PAY)) {
            // 如果订单是待支付状态，改为超时取消状态，更新订单对象
            courseOrder.setStatusOrder(CourseOrder.STATE_OVERTIME_CANCEL);
            courseOrder.setUpdateTime(new Date());
            updateById(courseOrder);
            log.info("取消课程订单：{}", courseOrder);

            // 如果是秒杀订单，还要退库存
            if (courseOrder.getOrderType() == 1){
                List<CourseOrderItem> courseOrderItems = courseOrderItemService.selectList(
                        new EntityWrapper<CourseOrderItem>().eq("order_no", orderNo));
                CourseOrderItem courseOrderItem = courseOrderItems.get(0);

                RSemaphore semaphore = redissonClient.getSemaphore(RedisKeyConstant.KILL_COURSE_STORE + courseOrderItem.getKillCourseId());
                semaphore.release(courseOrder.getTotalCount());
                log.info("订单服务，支付超时，秒杀订单退库存：{}",  courseOrder);
            }
        }
    }

}
